
import akshare as ak
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import ttk
from datetime import datetime
import math
import os

class GoldETFBacktest:
    def __init__(self, root):
        self.root = root
        self.root.title("盈亏比计算系统")

        # 初始化参数
        # 股票代码
        self.sk_code = 0

        # 最高价
        self.high_price = 0

        # 最低价
        self.low_price = 0

        # 三个盈亏比3:1,4:1,5:1
        self.plr1 = 0
        self.plr2 = 0
        self.plr3 = 0

        # 创建GUI组件
        self.create_widgets()

    def add_param_widgets(self):
        # 输入参数区域
        stock_frame = ttk.LabelFrame(self.root, text="股票信息", padding=10)
        stock_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nswe")

        # 股票代码
        ttk.Label(stock_frame, text="股票代码:").grid(row=0, column=0, pady=5, sticky="e")
        self.stock_code = ttk.Entry(stock_frame)
        self.stock_code.grid(row=0, column=1, padx=10, pady=5, sticky="w")
        self.stock_code.insert(0, "000688") # 518880 黄金ETF; 000688 科创50

        # 股票类型
        ttk.Label(stock_frame, text="类型:").grid(row=0, column=2, pady=5, sticky="e")
        self.combo = ttk.Combobox(stock_frame)
        self.combo.grid(row=0, column=3, padx=10, pady=5)
        self.combo['values'] = ('指数', '股票', 'ETF')
        # 显示第一个选项
        self.combo.current(0)

        # 时间范围
        ttk.Label(stock_frame, text="开始日期:").grid(row=1, column=0, pady=5, sticky="e")
        self.start_date = ttk.Entry(stock_frame)
        self.start_date.grid(row=1, column=1, padx=10, pady=5, sticky="w")
        self.start_date.insert(0, "20250101")

        ttk.Label(stock_frame, text="结束日期:").grid(row=1, column=2, pady=5, sticky="e")
        self.end_date = ttk.Entry(stock_frame)
        self.end_date.grid(row=1, column=3, padx=10, pady=5, sticky="w")
        self.end_date.insert(0, "20250821")

    def add_trade_rules_widgets(self):
        # 输入参数区域
        rules_frame = ttk.LabelFrame(self.root, text="盈亏比", padding=10)
        rules_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nswe")

        #=============================================================================================
        # 盈亏比设置
        ttk.Label(rules_frame, text="最高价:").grid(row=0, column=0, pady=5, sticky="e")
        self.high_wg = ttk.Entry(rules_frame)
        self.high_wg.grid(row=0, column=1, padx=10, pady=5, sticky="w")
        self.high_wg.insert(0, "1140.37")

        ttk.Label(rules_frame, text="最低价:").grid(row=0, column=2, pady=5, sticky="e")
        self.low_wg = ttk.Entry(rules_frame)
        self.low_wg.grid(row=0, column=3, padx=10, pady=5, sticky="w")
        self.low_wg.insert(0, "952.33")

        # 运行按钮
        self.run_button = ttk.Button(rules_frame, text="计算", command=self.draw_trading_range)
        self.run_button.grid(row=2, column=1, columnspan=2, padx=10, pady=5, sticky="w")

    def add_trade_widgets(self):
        # 结果显示区域
        result_frame = ttk.LabelFrame(self.root, text="计算结果", padding=10)
        result_frame.grid(row=2, column=0, padx=10, sticky="nswe")

        self.result_text = tk.Text(result_frame, height=20, width=70)
        self.result_text.grid(row=0, column=0, pady=5)

    def add_charts_widgets(self):
        # 图表区域
        chart_frame = ttk.LabelFrame(self.root, text="交易区间", padding=10)
        chart_frame.grid(row=0, column=2, rowspan=3, columnspan=2, padx=10, pady=10, sticky="n")

        # 设置Matplotlib全局参数
        plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定中文字体
        plt.rcParams['axes.unicode_minus'] = False    # 解决负号显示问题

        self.fig, self.ax = plt.subplots(figsize=(10, 6))

        self.canvas = FigureCanvasTkAgg(self.fig, master=chart_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        plt.close()

        # 配置网格布局
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(2, weight=1)

    def create_widgets(self):

        self.add_param_widgets()

        self.add_trade_rules_widgets()

        self.add_trade_widgets()

        self.add_charts_widgets()

    #
    # 获取指数列表：
    #   A股：ak.index_stock_info()/ak.stock_zh_index_spot_sina()
    #   港股：ak.stock_hk_index_spot_em()/ak.stock_hk_index_spot_sina()
    #   df[df['名称'] == '恒生科技指数']
    # 获取指定指数信息：
    #   A股：新浪：ak.stock_zh_index_daily("sh000001")  东财：ak.index_zh_a_hist("000001")
    #   港股：新浪：ak.stock_hk_index_daily_em(symbol="HSTECH")
    #         东财：ak.stock_hk_index_daily_sina(symbol="CES100")
    #
    # 获取股票列表：
    #   ak.stock_zh_a_spot()/ak.stock_zh_a_spot_em()/ak.stock_info_a_code_name()
    # 获取指定股票：
    #   ak.stock_zh_a_hist(symbol="000001")
    #
    # 获取ETF列表：
    #   东财: fund_etf_spot_em()
    #   
    # 获取指定ETF：
    #   fund_etf_hist_sina(symbol="sh000001")
    #   fund_etf_hist_em(symbol="000001")
    #
    def get_stock_info(self):
        """获取ETF数据"""
        start_date = self.start_date.get()
        end_date = self.end_date.get()
        stock_code = self.stock_code.get()
        stock_type = self.combo.get()

        self.sk_code = stock_code

        try:
            # 获取ETF数据
            file_path = ("%s_%s.csv") % (stock_type, stock_code)
            read_stock_csv = 1

            if os.path.exists(file_path):
                mtime = os.path.getmtime(file_path)
                file_date = datetime.fromtimestamp(mtime).date()
                # 获取今天日期
                today = datetime.now().date()

                if file_date != today:
                    read_stock_csv = 0
            else:
                read_stock_csv = 0

            if read_stock_csv == 1:
                df = pd.read_csv(file_path)
            else:
                if stock_type == "股票":
                    df = ak.stock_zh_a_hist(symbol=f"{stock_code}")
                elif stock_type == "指数":
                    df = ak.index_zh_a_hist(symbol=f"{stock_code}")
                elif stock_type == "ETF":
                    df = ak.fund_etf_hist_em(symbol=f"{stock_code}")

                # 写入csv文件
                df.to_csv(file_path)

            # 日期,开盘,收盘,最高,最低
            if "日期" in df.columns:
                df = df.rename(columns={"日期":"date","开盘":"open","收盘":"close","最高":"high","最低":"low"})

            df['date'] = pd.to_datetime(df['date'])
            df.set_index('date', inplace=True)

            # 筛选日期范围
            start_date = datetime.strptime(start_date, "%Y%m%d")
            end_date = datetime.strptime(end_date, "%Y%m%d")
            df = df[(df.index >= start_date) & (df.index <= end_date)]

            return df
        except Exception as e:
            self.result_text.insert(tk.END, f"获取数据失败: {str(e)}\n")
            return None

    def cal_plr_value(self, df):

        # 盈亏比3:1
        self.plr1 = (self.high_price - self.low_price) / 4 + self.low_price

        # 盈亏比4:1
        self.plr2 = (self.high_price - self.low_price) / 5 + self.low_price

        # 盈亏比5:1
        self.plr3 = (self.high_price - self.low_price) / 6 + self.low_price

        df['price_line_1'] = self.plr1
        df['price_line_2'] = self.plr2
        df['price_line_3'] = self.plr3

        df['high_line'] = self.high_price
        df['low_line'] = self.low_price

        return df

    def draw_trading_range(self):
        """运行回测"""
        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, "交易规则如下：\n\n")
        self.root.update()

        # 获取数据
        df = self.get_stock_info()
        if df is None or df.empty:
            self.result_text.insert(tk.END, "获取数据失败或数据为空\n")
            return

        # 获取参数
        try:

            # 最高价
            self.high_price = float(self.high_wg.get()) if self.high_wg.get() else 0
            # 最低价
            self.low_price = float(self.low_wg.get()) if self.low_wg.get() else 0

        except ValueError:
            self.result_text.insert(tk.END, "参数输入错误，请检查输入值\n")
            return

        # 计算盈亏比对应的数值
        df = self.cal_plr_value(df)

        # 显示日志
        self.show_trade_log(df)

        # 绘制图表
        self.plot_results(df)

    def show_trade_log(self, df):

        self.show_profit_loss_log(df)

        self.show_defense_log(df)

    def show_profit_loss_log(self, df):

        self.result_text.insert(tk.END, f"时间参考范围: {df.index[0].date()} ~ {df.index[-1].date()}\n")
        self.result_text.insert(tk.END, "================\n")
        self.result_text.insert(tk.END, f"最高价: {self.high_price:.2f}\n")
        self.result_text.insert(tk.END, f"最低价: {self.low_price:.2f}\n")
        self.result_text.insert(tk.END, "================\n")

    def show_defense_log(self, df):

        pr1 = self.high_price * 100 / self.plr1 - 100
        ls1 = 100 - self.low_price * 100 / self.plr1

        pr2 = self.high_price * 100 / self.plr2 - 100
        ls2 = 100 - self.low_price * 100 / self.plr2

        pr3 = self.high_price * 100 / self.plr3 - 100
        ls3 = 100 - self.low_price * 100 / self.plr3

        self.result_text.insert(tk.END, "================\n")
        self.result_text.insert(tk.END, f"盈亏比(3:1): {self.plr1:.2f} 止盈：+{pr1:.2f}% 止损：-{ls1:.2f}%\n")
        self.result_text.insert(tk.END, f"盈亏比(4:1): {self.plr2:.2f} 止盈：+{pr2:.2f}% 止损：-{ls2:.2f}%\n")
        self.result_text.insert(tk.END, f"盈亏比(5:1): {self.plr3:.2f} 止盈：+{pr3:.2f}% 止损：-{ls3:.2f}%\n")
        self.result_text.insert(tk.END, "================\n")


    # 绘制图表函数
    def plot_results(self, df):
        """绘制回测结果图表"""
        self.ax.clear()

        # 绘制价格
        self.ax.plot(df.index, df['close'], label='价格', color='black', linewidth=1)

        self.ax.plot(df.index, df['price_line_1'], label=f'盈亏比3:1({self.plr1:.2f})', color='blue', linewidth=1)
        self.ax.plot(df.index, df['price_line_2'], label=f'盈亏比4:1({self.plr2:.2f})', color='blue', linewidth=1)
        self.ax.plot(df.index, df['price_line_3'], label=f'盈亏比5:1({self.plr3:.2f})', color='blue', linewidth=1)

        self.ax.plot(df.index, df['high_line'], label=f'止盈线({self.high_price:.2f})', color='red', linestyle='-', linewidth=1)
        self.ax.plot(df.index, df['low_line'], label=f'止损线({self.low_price:.2f})', color='red', linestyle=':', linewidth=1)

        # 设置图表属性
        self.ax.set_title(f'K线图({self.sk_code})')
        self.ax.set_xlabel('日期')
        self.ax.set_ylabel('价格 (元)')
        self.ax.legend()
        self.ax.grid(True)

        # 自动调整日期显示
        self.fig.autofmt_xdate()

        self.canvas.draw()

        # 画完关闭图表
        plt.close()

if __name__ == "__main__":
    root = tk.Tk()
    app = GoldETFBacktest(root)
    root.mainloop()